package com.insigma.business.gateway.template;

import com.alibaba.fastjson.JSONObject;

import com.alibaba.fastjson.JSON;
import com.insigma.business.common.utils.AnnotationUtils;
import com.insigma.business.gateway.dto.MethodClassDTO;
import com.insigma.business.qrcode.common.annotation.ApiServiceType;
import com.insigma.business.gateway.dto.ApiServiceContext;
import com.insigma.business.gateway.dto.ServiceInfo;
import com.insigma.business.gateway.dto.SpiConstant;
import com.insigma.business.gateway.entity.ApiServiceCallerDO;
import com.insigma.business.qrcode.common.dto.ApiConstant;
import com.insigma.business.qrcode.common.util.AccessFailedException;
import com.insigma.business.qrcode.common.util.BizCheckUtil;
import com.insigma.business.qrcode.common.util.ConsumerCodeEnum;
import com.insigma.business.qrcode.common.util.ExceptionUtil;
import com.insigma.business.qrcode.common.vo.BaseResponse;
import com.insigma.business.gateway.api.*;
import com.insigma.business.qrcode.soldier.dto.GatewayTypeEnum;
import com.insigma.business.qrcode.soldier.dto.RequestObjFO;
import com.insigma.business.qrcode.soldier.entity.PartnerSecretDO;
import com.insigma.business.qrcode.soldier.service.ApiAdapterService;
import com.insigma.business.qrcode.soldier.service.PartnerSecretService;
import com.insigma.business.qrcode.soldier._consumer.SecurityConsumer;
import com.insigma.business.qrcode.soldier.service.impl.TrafficComponent;
import com.insigma.business.qrcode.soldier.util.SecurityUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;


@Slf4j
public class BaseGatewayManagerImpl {

    
    private Map<String, ApiAdapterService> productionAppCode2ApiServiceMap = new HashMap<>();
    private Map<Method, MethodClassDTO> methodClassMap = new HashMap<>();

    @Autowired
    private ApiServiceCallerService apiServiceCallerService;
    @Autowired
    private PartnerSecretService partnerSecretService;
    @Autowired
    SecurityConsumer securityConsumer;
    @Autowired
    ApiAdapterService apiAdapterService;

    @Autowired
    private TrafficComponent trafficComponent;
    @Autowired
    private ServiceInvoker serviceInvoker;
    
    protected void init(final GatewayTypeEnum gatewayTypeEnum) {
        final List<ApiServiceCallerDO> apiServiceCallerCOList = apiServiceCallerService.listApiServiceCallerByGwType(gatewayTypeEnum.name());
        for (ApiServiceCallerDO apiServiceCallerCO : apiServiceCallerCOList) {
            try {
                BizCheckUtil.checkIsTrue(StringUtils.isNotBlank(apiServiceCallerCO.getGatewayType()) && StringUtils.isNotBlank(apiServiceCallerCO.getGroupName()) &&
                                StringUtils.isNotBlank(apiServiceCallerCO.getInterfacePath()) && StringUtils.isNotBlank(apiServiceCallerCO.getProductionAppCode())
                                && apiServiceCallerCO.getProductionAppCode().trim().length() == 4 &&
                                StringUtils.equals(Class.forName(apiServiceCallerCO.getInterfacePath()).getName(), ApiAdapterService.class.getName()),
                        "数据库" + gatewayTypeEnum.getTitle() + "配置错误，请检查！");
            } catch (ClassNotFoundException e) {
                ExceptionUtil.caught(log, e, "编程方式构建dubbo消费端对象失败");
                throw new AccessFailedException(ConsumerCodeEnum.INTERNAL_ERROR.getCode(),
                        "数据库" + gatewayTypeEnum.getTitle() + "配置错误，请检查！");
            }

            if (productionAppCode2ApiServiceMap.containsKey(apiServiceCallerCO.getGatewayType() + ":" + apiServiceCallerCO.getProductionAppCode())) {
                throw new AccessFailedException(ConsumerCodeEnum.INTERNAL_ERROR.getCode(),
                        "数据库" + gatewayTypeEnum.getTitle() + "配置错误，请检查！");
            } else {
                //初始化注册所有带ApiServiceType注解的方法
                productionAppCode2ApiServiceMap.put(
                        apiServiceCallerCO.getGatewayType() + ":" + apiServiceCallerCO.getProductionAppCode(),
                        this.initCreateReference());
            }
        }
    }

    
    protected void checkRequestParam(final RequestObjFO requestObj, final GatewayTypeEnum gatewayTypeEnum, final boolean isVerifySign) {
        checkParam(requestObj);
        checkIsJsonStr(requestObj.getBizReqData());

        final PartnerSecretDO partnerSecretCO = partnerSecretService.getByPartnerNoAndGwType(requestObj.getPartnerId(), gatewayTypeEnum.name());
        BizCheckUtil.checkIsTrue(partnerSecretCO != null, "商户号不存在");
        BizCheckUtil.checkIsTrue(StringUtils.equals(requestObj.getSource(), partnerSecretCO.getEndType()), "终端类型错误");

        if (isVerifySign) {
            verifySign(requestObj, partnerSecretCO.getSecret());
        }

        //暂时移除接口非法校验
        if (false) {
            BizCheckUtil.checkIsTrue(productionAppCode2ApiServiceMap
                    .containsKey(gatewayTypeEnum.name() + ":" + parseProductionAppCode(requestObj.getOperation())), "当前请求的接口非法");
        }
    }

    @SneakyThrows
    public BaseResponse executeService(final RequestObjFO requestObj, final GatewayTypeEnum gatewayTypeEnum) {
        final String productionAppCode = parseProductionAppCode(requestObj.getOperation());
        
        final String apiServiceType = parseApiServiceType(requestObj.getOperation());
        addTraffic(apiServiceType);

        final ServiceInfo serviceInfo = this.queryApiServiceInfo(productionAppCode, apiServiceType, gatewayTypeEnum);
        final ApiServiceContext apiServiceContext = buildApiServiceContext(apiServiceType, serviceInfo);
        return apiAdapterService.invokeApiService(apiServiceContext, requestObj.getBizReqData());
    }

    private void addTraffic(String apiServiceType) {
        trafficComponent.incTrafficCount(apiServiceType);
    }


    
    private void checkIsJsonStr(final String jsonStr) {
        try {
            JSON.parse(jsonStr);
        } catch (Exception e) {
            throw new AccessFailedException(ConsumerCodeEnum.ILLEGAL_PARAM.getCode(), ConsumerCodeEnum.ILLEGAL_PARAM.getDesc());
        }
    }

    
    private ServiceInfo queryApiServiceInfo(final String productionAppCode,
                                            final String apiServieType,
                                            final GatewayTypeEnum gatewayTypeEnum) {
        List<ServiceInfo> serviceInfoList = new ArrayList<>();
        for (Map.Entry<Method, MethodClassDTO> methodStringEntry : this.methodClassMap.entrySet()) {
            ServiceInfo serviceInfo = new ServiceInfo();
            serviceInfo.setInterfaceType(methodStringEntry.getValue().getClassBeanName());
            serviceInfo.setMethodName(methodStringEntry.getKey().getName());
            Class<?>[] clazzArray = methodStringEntry.getKey().getParameterTypes();
            List<String> classCanonicalNameList = new ArrayList<>(clazzArray.length);
            for (Class<?> clazz : clazzArray) {
                classCanonicalNameList.add(clazz.getCanonicalName());
            }
            serviceInfo.setParamType(classCanonicalNameList);
            Annotation[] ans = methodStringEntry.getKey().getAnnotations();
            Map<String, String> anMap = new HashMap<String, String>();
            for (Annotation an : ans) {
                if (an instanceof ApiServiceType) {
                    anMap.put(SpiConstant.KEY_API_TYPE, ((ApiServiceType) an).value());
                    anMap.put(SpiConstant.KEY_API_DESC, ((ApiServiceType) an).desc());
                    anMap.put(SpiConstant.KEY_API_NAME, ((ApiServiceType) an).name());
                }
            }
            serviceInfo.setAnnotations(anMap);
            serviceInfoList.add(serviceInfo);
        }
        for (ServiceInfo serviceInfo : serviceInfoList) {
            if (StringUtils.equals(apiServieType, serviceInfo.getAnnotations().get(SpiConstant.KEY_API_TYPE))) {
                return serviceInfo;
            }
        }
        throw new AccessFailedException(ConsumerCodeEnum.INTERNAL_ERROR.getCode(), "未找到API服务");
    }


    private ApiServiceContext buildApiServiceContext(final String apiServieType,
                                                     final ServiceInfo serviceInfo) {
        final ApiServiceContext apiServiceContext = new ApiServiceContext();
        apiServiceContext.setApiServiceType(apiServieType);
        
        apiServiceContext.setClassName(serviceInfo.getInterfaceType());
        apiServiceContext.setMethodName(serviceInfo.getMethodName());

        return apiServiceContext;
    }

    
    private void checkParam(final RequestObjFO requestObj) {
        BizCheckUtil.checkNotBlank(requestObj.getPartnerId(), "访问参数-商户号-非法");
        BizCheckUtil.checkNotBlank(requestObj.getSource(), "访问参数-客户端来源-非法");
        BizCheckUtil.checkNotBlank(requestObj.getOperation(), "访问参数-操作-非法");
        BizCheckUtil.checkNotBlank(requestObj.getBizReqData(), "访问参数-业务参数-非法");
        BizCheckUtil.checkNotBlank(requestObj.getTimestamp(), "访问参数-客户端时间戳-非法");
    }

    
    private void verifySign(final RequestObjFO requestObj, final String secret) {
        final Map<String, String> sortedRequestParamMap = new LinkedHashMap<>(6);
        sortedRequestParamMap.put(ApiConstant.BIZ_REQUEST_DATA, requestObj.getBizReqData());
        sortedRequestParamMap.put(ApiConstant.OPERATION, requestObj.getOperation());
        sortedRequestParamMap.put(ApiConstant.PARTNER_ID, requestObj.getPartnerId());
        sortedRequestParamMap.put(ApiConstant.SOURCE, requestObj.getSource());
        sortedRequestParamMap.put(ApiConstant.TIMESTAMP, requestObj.getTimestamp());

        BizCheckUtil.checkIsTrue(StringUtils.equals(requestObj.getSign(), SecurityUtil.signRequest(sortedRequestParamMap, secret)), "验签失败");
    }

    
    private String parseProductionAppCode(final String operation) {
        return operation.substring(0, operation.indexOf(ApiConstant.SPLIT_CHAR_DOT));
    }

    
    private String parseApiServiceType(final String operation) {
        return operation.substring(operation.indexOf(ApiConstant.SPLIT_CHAR_DOT) + 1);
    }

    ApiAdapterService initCreateReference(){
        this.methodClassMap = AnnotationUtils.handlePackageClassMap("com.insigma.business");
        serviceInvoker.registerService(methodClassMap);
        return null;
    }
}
